#Region "Imports"
Imports System.IO
#End Region
Friend Class ClsPdfReader
#Region "Private members"
    Private _pdfReader As clsPDFBaseReader
    Private _startXref As Integer
    Private _countXref As Integer 'Number of standard cross-reference table
    Private _lastXrefTablePosition As Long
    Private _trailerDictFromStream As clsPdfDictionnary 'Trailer entries in cross reference stream
    Private _hasCrossReferenceStream As Boolean 'True if the PDF use cross-reference streams
#End Region
#Region "Constructors"
    Public Sub New(ByVal fichierPath As String)
        _pdfReader = New clsPDFBaseReader(fichierPath)
        _trailerDictFromStream = Nothing
        _hasCrossReferenceStream = False
        _countXref = 0
    End Sub
#End Region
#Region "Public Properties"
    Public ReadOnly Property HasCrossReferenceStream As Boolean
        Get
            HasCrossReferenceStream = _hasCrossReferenceStream
        End Get
    End Property
    Public Property LastXrefTablePosition() As Long
        Get
            LastXrefTablePosition = _lastXrefTablePosition
        End Get
        Set(value As Long)
            _lastXrefTablePosition = value
        End Set
    End Property
    Public ReadOnly Property Reader() As clsPDFBaseReader
        Get
            Reader = _pdfReader
        End Get
    End Property
    Public ReadOnly Property TrailerFromStream As clsPdfDictionnary
        Get
            TrailerFromStream = _trailerDictFromStream
        End Get
    End Property
#End Region
#Region "Public methods"
    Public Function ReadCatalog(ByVal position As Integer) As clsPdfDictionnary
        Dim obj As clsPdfBaseObject
        ReadCatalog = Nothing
        Try
            Reader.ReaderPosition = position
            obj = Reader.GetObject
            If obj.Type = PdfObjectTypes.IndirectObject Then
                obj.ParseChildObjects()
                If obj.ChildsCount = 1 Then
                    ReadCatalog = obj.child(0).GetPdfDictionnary
                End If
            End If

        Catch ex As Exception
            ReadCatalog = Nothing
        End Try
    End Function
    Public Function ReadBottomCrossReferenceTable(ByRef xrefTable As Hashtable) As Boolean
        ReadBottomCrossReferenceTable = False
        Try
            _startXref = _getStartXrefPosition()
            If _startXref <> -1 Then
                _lastXrefTablePosition = _startXref
                _pdfReader.ReaderPosition = _startXref - 1
                ReadBottomCrossReferenceTable = ReadCrossReferenceTable(xrefTable)
            End If
        Catch ex As Exception
            ReadBottomCrossReferenceTable = False
        End Try
    End Function
    Public Function ReadCrossReferenceTable(ByRef xrefTable As Hashtable) As Boolean
        Dim position, posXRef As Integer
        ReadCrossReferenceTable = False
        Try
            Dim word As String
            posXRef = _pdfReader.ReaderPosition

            word = _pdfReader.GetWord()
            If word = PDF_XREF Then
                While Not _pdfReader.Eof
                    _countXref += 1
                    ReadCrossReferenceTable(_pdfReader.ReaderPosition, xrefTable)
                    position = _pdfReader.ReaderPosition
                    word = _pdfReader.GetWord()
                    If word = PDF_TRAILER Then
                        Exit While
                    Else
                        _pdfReader.ReaderPosition = position
                    End If
                End While
                ReadCrossReferenceTable = True
            Else
                'Maybe it is a cross-reference stream
                _pdfReader.ReaderPosition = posXRef
                _lastXrefTablePosition = posXRef
                Dim obj As clsPdfBaseObject
                obj = _pdfReader.GetObject
                If obj.Type = PdfObjectTypes.IndirectObject Then
                    obj.ParseChildObjects()
                    If obj.ChildsCount = 2 Then
                        If obj.child(0).Type = PdfObjectTypes.DictionaryObject And obj.child(1).Type = PdfObjectTypes.StreamObject Then
                            'We have a cross-reference stream
                            ReadCrossReferenceTable = ReadCrossReferenceTableStream(obj, xrefTable)
                        End If
                    End If
                End If
            End If
        Catch ex As Exception
            ReadCrossReferenceTable = False
        End Try
    End Function
    Friend Function ReadCrossReferenceTableStream(ByVal xrefstream As clsPdfBaseObject, ByRef xrefTable As Hashtable) As Boolean
        Dim Xrefdict As clsPdfDictionnary
        Dim stream As clsPdfStreamObject
        Dim buffer As Byte()
        Dim xref As ClsPdfXref
        Dim obj As clsPdfBaseObject
        Dim W As List(Of clsPdfBaseObject)
        Dim index As List(Of clsPdfBaseObject)
        Dim f1, f2, f3, type, i, size As Long
        Dim objNumber, genNumber, objIndex, firstObjNumber As Long
        Dim offset As Integer
        Dim j, k, currentSubSection As Integer
        Dim subsection As Long(,)

        ReadCrossReferenceTableStream = False
        _hasCrossReferenceStream = True
        Xrefdict = xrefstream.child(0).GetPdfDictionnary
        If Xrefdict.Item(PDF_TYPE).GetString <> PDF_XREF_STREAM Then
            'Finally it is not a cross reference stream
            Exit Function
        End If
        stream = New clsPdfStreamObject(Xrefdict, xrefstream.child(1))
        buffer = stream.GetDecodedData

        size = stream.Dictionnary.Item(PDF_SIZE).GetLong

        'Get the index for the first object number
        index = stream.Dictionnary.GetArray(PDF_INDEX)
        If Not index Is Nothing Then
            ReDim subsection(CInt(index.Count / 2) - 1, 1)
            j = 0
            k = 0
            While j < index.Count
                subsection(k, 0) = index(j).GetLong
                j += 1
                subsection(k, 1) = index(j).GetLong
                j += 1
                k += 1
            End While

            currentSubSection = 0
            firstObjNumber = subsection(currentSubSection, 0)
        Else
            ReDim subsection(1, 1)
            subsection(1, 1) = size
            firstObjNumber = 0
        End If

        'We get the length of fields
        W = stream.Dictionnary.GetArray(PDF_W)
        f1 = W(0).GetLong
        f2 = W(1).GetLong
        f3 = W(2).GetLong

        i = 0
        Do
            If subsection(currentSubSection, 1) = 0 Then
                currentSubSection += 1
                firstObjNumber = subsection(currentSubSection, 0)
            End If

            If f1 = 0 Then
                'If type field is not present, it defaults to type 1.
                type = 1
            Else
                type = _getLongFromWField(f1, buffer, i)
                i += f1
            End If

            xref = New ClsPdfXref
            Select Case type
                Case 0
                    objNumber = _getLongFromWField(f2, buffer, i)
                    i += f2

                    If f3 <> 0 Then
                        genNumber = _getLongFromWField(f3, buffer, i)
                        i += f3
                    Else
                        genNumber = 0
                    End If

                    xref.ObjectNumber = firstObjNumber
                    xref.GenerationNumber = genNumber
                    xref.Free = True
                    xref.Compressed = False
                Case 1
                    xref.ObjectNumber = firstObjNumber

                    offset = CInt(_getLongFromWField(f2, buffer, i))
                    i += f2

                    If f3 <> 0 Then
                        genNumber = _getLongFromWField(f3, buffer, i)
                        i += f3
                    Else
                        genNumber = 0
                    End If

                    xref.Free = False
                    xref.Compressed = False
                    xref.Offset = offset
                    xref.GenerationNumber = genNumber
                Case 2
                    objNumber = _getLongFromWField(f2, buffer, i)
                    i += f2

                    If f3 <> 0 Then
                        objIndex = _getLongFromWField(f3, buffer, i)
                        i += f3
                    Else
                        objIndex = 0
                    End If

                    xref.Free = False
                    xref.Compressed = True
                    xref.ObjectNumber = firstObjNumber
                    xref.ObjectStreamNumber = objNumber
                    xref.IndexCompressed = objIndex

                Case Else
                    Throw New Exception("Error while reading the Cross-Reference Stream Data")
            End Select
            firstObjNumber += 1

            If Not xrefTable.ContainsKey(xref.ObjectNumber) Then
                'We always keep the most recent object
                xrefTable.Add(xref.ObjectNumber, xref)
            End If

            subsection(currentSubSection, 1) -= 1

        Loop While i < buffer.Length

        ReadCrossReferenceTableStream = True

        'We add the dictionnary entries pertaining to the trailer in the trailer dictionnary

        If _trailerDictFromStream Is Nothing Then
            _trailerDictFromStream = New clsPdfDictionnary
        End If
        If stream.Dictionnary.ContainsKey(PDF_ROOT) Then
            obj = stream.Dictionnary.Item(PDF_ROOT)
            _trailerDictFromStream.Add(PDF_ROOT, obj, False)
        End If
        If stream.Dictionnary.ContainsKey(PDF_ENCRYPT) Then
            obj = stream.Dictionnary.Item(PDF_ENCRYPT)
            _trailerDictFromStream.Add(PDF_ENCRYPT, obj, False)
        End If
        If stream.Dictionnary.ContainsKey(PDF_INFO) Then
            obj = stream.Dictionnary.Item(PDF_INFO)
            _trailerDictFromStream.Add(PDF_INFO, obj, False)
        End If


        If stream.Dictionnary.ContainsKey(PDF_PREV) Then
            'We have another cross-reference stream
            _pdfReader.ReaderPosition = stream.Dictionnary.Item(PDF_PREV).GetLong
            obj = _pdfReader.GetObject
            If obj.Type = PdfObjectTypes.IndirectObject Then
                obj.ParseChildObjects()
                If obj.ChildsCount = 2 Then
                    If obj.child(0).Type = PdfObjectTypes.DictionaryObject And obj.child(1).Type = PdfObjectTypes.StreamObject Then
                        'We have a cross-reference stream
                        ReadCrossReferenceTableStream = ReadCrossReferenceTableStream(obj, xrefTable)
                    End If
                End If
            End If
        End If
    End Function
    Public Function ReadTrailer(ByVal position As Integer) As clsPdfBaseObject
        Dim obj As clsPdfBaseObject
        _pdfReader.ReaderPosition = position
        obj = _pdfReader.GetObject()
        If obj.Type <> PdfObjectTypes.DictionaryObject Then
            Throw New Exception("Error while reading the trailer")
        End If
        ReadTrailer = obj
    End Function
#End Region
#Region "Private methods"
    Private Function _getLongFromWField(fieldsize As Long, ByRef buffer As Byte(), position As Long) As Long
        Select Case fieldsize
            Case 1
                Dim b(1) As Byte
                b(0) = buffer(CInt(position))
                b(1) = 0
                _getLongFromWField = BitConverter.ToUInt16(b, 0)
            Case 2
                Dim b(1) As Byte
                b(0) = buffer(CInt(position) + 1)
                b(1) = buffer(CInt(position))
                _getLongFromWField = BitConverter.ToUInt16(b, 0)
            Case 3
                Dim b(3) As Byte
                b(0) = buffer(CInt(position) + 2)
                b(1) = buffer(CInt(position) + 1)
                b(2) = buffer(CInt(position))
                b(3) = 0
                _getLongFromWField = BitConverter.ToUInt32(b, 0)
            Case 4
                Dim b(3) As Byte
                b(0) = buffer(CInt(position) + 3)
                b(1) = buffer(CInt(position) + 2)
                b(2) = buffer(CInt(position) + 1)
                b(3) = buffer(CInt(position))
                _getLongFromWField = BitConverter.ToUInt32(b, 0)
            Case Else
                Throw New Exception("Error while reading the Cross-Reference Stream Data")
        End Select
    End Function
    Friend Sub ReadCrossReferenceTable(ByVal position As Integer, ByRef xrefTable As Hashtable)
        Dim obj As clsPdfBaseObject
        Dim startNumber As Long
        Dim entryCount As Long

        ' _pdfReader.ReaderPosition = position - 1
        _pdfReader.ReaderPosition = position

        obj = _pdfReader.GetObject()
        If obj.Type <> PdfObjectTypes.NumericObject Then
            Throw New Exception("Error while reading a cross-reference table")
        End If
        startNumber = obj.GetLong

        obj = _pdfReader.GetObject()
        If obj.Type <> PdfObjectTypes.NumericObject Then
            Throw New Exception("Error while reading a cross-reference table")
        End If
        entryCount = obj.GetLong
        _readCrossReferenceTableEntries(startNumber, entryCount, xrefTable)
    End Sub
    Private Sub _readCrossReferenceTableEntries(ByVal objNumber As Long, ByVal quantity As Long, ByRef xrefTable As Hashtable)
        Dim word As String
        Dim xref As ClsPdfXref
        Dim initialNumber As Long = objNumber
        While quantity > 0
            xref = New ClsPdfXref
            xref.ObjectNumber = objNumber
            objNumber += 1

            word = _pdfReader.GetWord()
            xref.Offset = CInt(word)

            word = _pdfReader.GetWord()
            xref.GenerationNumber = CLng(word)

            word = _pdfReader.GetWord()
            Select Case word
                Case PDF_FREEOBJECT
                    xref.Free = True
                    If _countXref = 1 And initialNumber = 1 Then
                        'The initial object number of the first standard cross-reference table must be 0
                        'If it is not, we must correct it.  This is a non-conforming PDF file.
                        xref.ObjectNumber = 0
                        objNumber -= 1
                        'Stop
                    End If
                Case PDF_USEDOBJECT
                    xref.Free = False
                Case Else
                    Throw New Exception("Error while reading a cross-reference table")
            End Select
            If Not xrefTable.ContainsKey(xref.ObjectNumber) Then
                'We always keep the most recent object
                xrefTable.Add(xref.ObjectNumber, xref)
            End If
            quantity -= 1
        End While
    End Sub
    Friend Function _getStartXrefPosition() As Integer

        _getStartXrefPosition = 0

        _pdfReader.ReaderPosition = _pdfReader._getStartXrefPosition
        Dim obj As clsPdfBaseObject
        obj = _pdfReader.GetObject()
        If obj.Type = PdfObjectTypes.NumericObject Then
            _getStartXrefPosition = obj.GetLong
        End If

    End Function

#End Region
End Class
